import {Object3D, Color, AnimationMixer, BufferGeometry,SphereGeometry, Line, LineBasicMaterial, MeshBasicMaterial, Mesh, Vector3,LoopOnce,Raycaster,Quaternion,
} from '../libs/three137/three.module.js';

import { SFX } from './SFX.js';
const { random, PI, abs, acos, tan, floor } = Math
class NPC {
	constructor(options) {
		const fps = options.fps || 30; //default fps

		this.name = options.name || 'NPC';

		this.animations = {};

		options.app.scene.add(options.object);

		this.object = options.object;
		this.pathLines = new Object3D();
		this.pathColor = new Color(0xFFFFFF);
		options.app.scene.add(this.pathLines);

		this.showPath = true || options.showPath;

		this.waypoints = options.waypoints;

		this.dead = false;

		this.speed = options.speed;
		this.app = options.app;

		if (options.app.pathfinder) {
			this.pathfinder = options.app.pathfinder;
			this.ZONE = options.zone;
			this.navMeshGroup = this.pathfinder.getGroup(this.ZONE, this.object.position);
		}

		const pt = this.object.position.clone();
		pt.z += 10;
		this.object.lookAt(pt);

		this.rifle = options.rifle;
		this.aim = options.aim;
		this.enemy = this.app.user.root;
		this.isFiring = false;
		this.raycaster = new Raycaster();
		this.temVec = new Vector3();
		this.forward = new Vector3(0, 0, 1);
		this.temQuat = new Quaternion();
		this.aggro = false;

		if (options.animations) {
			//Use this option to set multiple animations directly
			this.actions = [];
			this.mixer = new AnimationMixer(options.object);
			options.animations.forEach((animation) => {
				let key = animation.name.toLowerCase()
				this.animations[key] = animation;
				this.actions.push(key)
			})
			// this.newAni();
		}
		this.initRifleDirection();// npc的模型和玩家的差不多
	}


	initRifleDirection() {
		this.rifleDirection = {};

		this.rifleDirection.idle = new Quaternion(-0.044, -0.061, 0.865, 0.495);
		this.rifleDirection.firing = new Quaternion(-0.147, -0.040, 0.784, 0.600);
		this.rifleDirection.walking = new Quaternion(0.046, -0.017, 0.699, 0.712);
		this.rifleDirection.shot = new Quaternion(-0.133, -0.144, -0.635, 0.747);
	}




	initSounds() {
		this.sfx = new SFX(this.app.camera, '../assets/factory/sfx/', this.app.listener);
		this.sfx.load('footsteps', true, 0.6, this.object);
		this.sfx.load('groan', false, 0.6, this.object);
		this.sfx.load('shot', false, 0.6, this.object);

	}

	reset() {
		this.dead = false;
		this.object.position.copy(this.randomWaypoint);
		let pt = this.randomWaypoint;
		let count = 0;
		while (this.object.position.distanceToSquared(pt) < 1 && count < 10) {
			pt = this.randomWaypoint;
			count++;
		}
		this.newPath(pt);
	}
	get randomWaypoint() {
		const index = Math.floor(Math.random() * this.waypoints.length);
		return this.waypoints[index];
	}

	setTargetDirection(pt) {
		const player = this.object;
		pt.y = player.position.y;
		const quaternion = player.quaternion.clone();
		player.lookAt(pt);
		this.quaternion = player.quaternion.clone();
		player.quaternion.copy(quaternion);
	}

	newPath(pt) {
		const player = this.object;

		if (this.pathfinder === undefined) {
			this.calculatedPath = [pt.clone()];
			//Calculate target direction
			this.setTargetDirection(pt.clone());
			this.action = 'walking';
			return;
		}

		if (this.sfx) this.sfx.play('footsteps')

		//console.log(`New path to ${pt.x.toFixed(1)}, ${pt.y.toFixed(2)}, ${pt.z.toFixed(2)}`);	

		const targetGroup = this.pathfinder.getGroup(this.ZONE, pt);
		const closestTargetNode = this.pathfinder.getClosestNode(pt, this.ZONE, targetGroup);

		// Calculate a path to the target and store it
		this.calculatedPath = this.pathfinder.findPath(player.position, pt, this.ZONE, this.navMeshGroup);

		if (this.calculatedPath && this.calculatedPath.length) {
			this.action = 'walking';

			this.setTargetDirection(this.calculatedPath[0].clone());

			if (this.showPath) {
				if (this.pathLines) this.app.scene.remove(this.pathLines);

				const material = new LineBasicMaterial({
					color: this.pathColor,
					linewidth: 2
				});

				const points = [player.position];

				// Draw debug lines
				this.calculatedPath.forEach(function (vertex) {
					points.push(vertex.clone());
				});

				let geometry = new BufferGeometry().setFromPoints(points);

				this.pathLines = new Line(geometry, material);
				this.app.scene.add(this.pathLines);

				// Draw debug spheres except the last one. Also, add the player position.
				const debugPath = [player.position].concat(this.calculatedPath);

				debugPath.forEach(vertex => {
					geometry = new SphereGeometry(0.2);
					const material = new MeshBasicMaterial({ color: this.pathColor });
					const node = new Mesh(geometry, material);
					node.position.copy(vertex);
					this.pathLines.add(node);
				});
			}
		} else {
			this.action = 'idle';

			if (this.sfx) this.sfx.stop('footsteps');

			if (this.pathfinder) {
				const closestPlayerNode = this.pathfinder.getClosestNode(player.position, this.ZONE, this.navMeshGroup);
				const clamped = new Vector3();
				this.pathfinder.clampStep(
					player.position,
					pt.clone(),
					closestPlayerNode,
					this.ZONE,
					this.navMeshGroup,
					clamped);
			}

			if (this.pathLines) this.app.scene.remove(this.pathLines);
		}
	}

	// newAni() {
	// 	let ind;// 这里还真得用do while 
	// 	do {

	// 		ind = floor(random() * this.actions.length);
	// 	}
	// 	while (this.actions[ind] === this.actionName)
	// 	this.action = this.actions[ind];
	// }

	set action(name) {
		if (this.actionName == name.toLowerCase()) return;

		const clip = this.animations[name.toLowerCase()];

		if (clip !== undefined) {
			const action = this.mixer.clipAction(clip);
			if (name == 'shot') {
				action.clampWhenFinished = true;
				action.setLoop(LoopOnce);
				// 死了之后 停止当前路径 暂时没有清除的逻辑
				this.dead = true;
				if (this.sfx) {
					this.sfx.stop('footsteps');
					this.sfx.play('groan');
				}
				this.calculatedPath = null
			}

			action.reset();
			const nofade = this.actionName == 'shot';
			this.actionName = name.toLowerCase();
			action.play();
			if (this.curAction) {
				if (nofade) {
					this.curAction.enabled = false;
				} else {
					this.curAction.crossFadeTo(action, 0.5);
				}
			}
			this.curAction = action;
		}
		if (this.rifle && this.rifleDirection) {
			const q = this.rifleDirection[name.toLowerCase()];
			if (q) {
				const start = new Quaternion();
				start.copy(this.rifle.quaternion);
				this.rifle.quaternion.copy(q);
				this.rifle.rotateX(1.57);
				const end = new Quaternion();
				end.copy(this.rifle.quaternion)
				this.rotateRifle = { start, end, time: 0 };
				this.rifle.quaternion.copy(start);
			}
		}
	}

	get position() { return this.object.position };
	// user 到NPC的距离是否小于射程
	withinAggroRange() {
		const d = this.object.position.distanceToSquared(this.enemy.position);
		return d < 400;
	}

	// user 是否在npc的可视范围内 ，fov是视角，计算NPC的法向和user与NPC连线的 内积从而得出

	withinFOV(fov) {
		const angle = fov / 360 * PI; //这里已经除了2
		const v1 = this.forward.clone().applyQuaternion(this.object.quaternion);
		const v2 = this.enemy.position.clone().sub(this.object.position).normalize(); // 归一化 也是要平方开平方 这样来说，如果不是多次用到 直接求距离也是一样啊
		const a = acos(v1.dot(v2)); // angleTo   137的这个api 取了绝对值之后 再反三角 再乘2  不太理解 现在这个版本不需归一化，没取绝对值，
		return a < angle;
	}
		
	set firing(mode) {
		this.isFiring = mode;
		if (mode) {
			this.action = 'firingwalk'
			this.bulletTime = this.app.clock.getElapsedTime();
		} else {
			this.newPath(this.randomWaypoint)
		}
	}
	shoot() {
		// npc ammo no limit  还是共用一个bullethandler
		if (!this.bulletHandler) {	this.bulletHandler = this.app.bulletHandler;}
		this.aim.getWorldPosition(this.temVec);
		this.aim.getWorldQuaternion(this.temQuat);
		this.bulletHandler.createBullet(this.temVec, this.temQuat,true);
		this.bulletTime = this.app.clock.getElapsedTime();
		this.sfx.play('shot')
	}
	// 调转法向 瞄准  但是只瞄准10%   这是控制npc的枪法吗
	pointAtEnemy() {
		this.object.getWorldQuaternion(this.temQuat);
		this.object.lookAt(this.enemy.position);
		this.object.quaternion.slerp(this.temQuat, .9)
	}
	// 视线是否被遮挡
	seeEnemy() {
		const enemyVec = this.enemy.position.clone().sub(this.object.position);
		const enemyDistance = enemyVec.length();
		enemyVec.normalize();// 这里是不是可以手动实现，three没缓存length

		this.aim.getWorldPosition(this.temVec);
		this.raycaster.set(this.temVec, enemyVec); //从npc到user的射线  检测有无遮挡

		const intersects = this.raycaster.intersectObjects(this.app.factory.children);

		if (intersects.length) {
			return intersects[0].distance > enemyDistance; // 最近的遮挡在身后也可以
		}
		return true

	}

	// 两个问题 枪是斜的 ， 20 米警戒，10米设计的逻辑不行，npc 不会在见到敌人10m内就停下
	update(dt) {
		const speed = this.actionName === 'firingwalk' ? this.speed * .6 : this.speed;
		const player = this.object;

		if (this.mixer) this.mixer.update(dt);

		//  补间枪的转动
		if (this.rotateRifle) {
			this.rotateRifle.time += dt;
			if (this.rotateRifle.time > .5) {
				this.rifle.quaternion.copy(this.rotateRifle.end)
				this.rotateRifle = null;
			} else {
				this.rifle.quaternion.slerpQuaternions(this.rotateRifle.start, this.rotateRifle.end, this.rotateRifle.time)
			}
		}
		
		if (!this.dead && this.app.active && this.enemy && !this.enemy.userData.dead) {
			if (!this.aggro) { //是否有敌情  警戒范围 20  并且在视野内  
				if (this.withinAggroRange()) {
					if (this.withinFOV(120)) {
						this.aggro = true;
						const v = this.enemy.position.clone().sub(this.object.position);
						const len = v.length();
						if (len > 10) { // 射程10 不足就要拉近距离
							this.newPath(this.enemy.position);
						} else { // 足够的话可以停下了 
							this.calculatedPath = null;
							this.action = 'idle';
						}
					}
				}
			} else {  // 已经发现敌情  这样发现敌人到反应 有一帧的时间差
				if (this.withinAggroRange()) { // 上一帧的时候发现的 这一帧还在  不检测视野了 
					const v = this.enemy.position.clone().sub(this.object.position)
					const len = v.length();
					if (!this.isFiring) {
						if (len < 10) {
							this.calculatedPath = null;
							this.firing = true;
							this.action = 'firing';
						} else if (this.withinFOV(10)) { // 距离大于10 状态还是开火 但是 动作不变 
							this.firing = true;
						}
					} else {
						if (!this.calculatedPath) {
							this.pointAtEnemy()
						} else if (!this.withinFOV(10)) {
							this.isFiring = false;
							this.action = 'walking'
						}
						// .6s 后开枪
						if (this.isFiring && this.seeEnemy()) {
							const elapsedTime = this.app.clock.getElapsedTime() - this.bulletTime;
							if (elapsedTime > .6) this.shoot();
						}
					}

				} else {
					this.firing = false;
					this.aggro = false;
				}
			}
		} else if (this.isFiring) {
			this.firing = false;
			this.aggro = false;

		}




		if (this.calculatedPath && this.calculatedPath.length) {
			const targetPosition = this.calculatedPath[0];

			const vel = targetPosition.clone().sub(player.position);

			let pathLegComplete = (vel.lengthSq() < 0.01);

			if (!pathLegComplete) {
				//Get the distance to the target before moving
				const prevDistanceSq = player.position.distanceToSquared(targetPosition);
				vel.normalize();
				// Move player to target
				if (this.quaternion) player.quaternion.slerp(this.quaternion, 0.1);
				player.position.add(vel.multiplyScalar(dt * speed));
				//Get distance after moving, if greater then we've overshot and this leg is complete
				const newDistanceSq = player.position.distanceToSquared(targetPosition);
				pathLegComplete = newDistanceSq > prevDistanceSq;
			}

			if (pathLegComplete) {
				// Remove node from the path we calculated
				this.calculatedPath.shift();
				if (this.calculatedPath.length == 0) {
					if (this.waypoints !== undefined) { //  一条路线结束之后 随机开始新的路线
						this.newPath(this.randomWaypoint);
					} else { //  什么情况下 waypoints会未定义  这个是构造传进来的
						player.position.copy(targetPosition);
						this.action = 'idle';
					}
				} else {
					this.setTargetDirection(this.calculatedPath[0].clone());
				}
			}
		} else { // 战斗状态下 没有wayponts 也不开启新路线了 之前应该是走到射程内 清空了路径触发了这个
			if (!this.dead && this.waypoints !== undefined &&!this.aggro )  this.newPath(this.randomWaypoint);
		}

	}
}

export { NPC };